﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

namespace Argos.Diagnostics
{
	/// <summary>
	/// A utility class that is used to trace data with.
	/// </summary>
	/// <remarks>
	/// Tracing is done using the <see cref="TraceSource"/> class.
	/// </remarks>
	public static class Tracer
	{

		private static object SyncRoot = new object();
		private static Dictionary<string, TraceSource> Sources = new Dictionary<string, TraceSource>();



		#region public static KeyValuePair<string, object> CreateData(string name, object value)

		/// <summary>
		/// Creates a key-value pair from the given values.
		/// </summary>
		/// <remarks>
		/// When tracing data, it is sometimes useful to use this method to create data
		/// to trace, because a trace listener can then take advantage of the
		/// structured data.
		/// </remarks>
		/// <param name="name">The name of the data.</param>
		/// <param name="value">The value of the data.</param>
		public static KeyValuePair<string, object> CreateData(string name, object value)
		{
			return new KeyValuePair<string, object>(name, value);
		}

		#endregion

		#region public static TraceSource GetTraceSource(string name)

		/// <summary>
		/// Gets the trace source with the given name. If that does not exist, it will be created.
		/// </summary>
		/// <param name="name">The name of the trace source to get.</param>
		public static TraceSource GetTraceSource(string name)
		{
			lock (SyncRoot)
			{
				if (!Sources.ContainsKey(name))
				{
					Sources[name] = new TraceSource(name);
				}
			}
			return Sources[name];
		}

		#endregion

		#region public static void TraceData(TraceEventType eventType, int id, params object[] data)

		/// <summary>
		/// Writes data to the configured trace listeners.
		/// </summary>
		/// <param name="eventType">The type of event to write.</param>
		/// <param name="id">A numeric identifier for the event.</param>
		/// <param name="data">The data to write.</param>
		[Conditional("TRACE")]
		public static void TraceData(TraceEventType eventType, int id, params object[] data)
		{
			StackTrace callStack = new StackTrace(1);
			TraceData(callStack, eventType, id, data);
		}

		#endregion
		
		#region public static void TraceMessage(TraceEventType eventType, int id, string message)

		/// <summary>
		/// Writes the given message to the configured trace listeners.
		/// </summary>
		/// <param name="eventType">The type of event to write.</param>
		/// <param name="id">A numeric identifier for the event.</param>
		/// <param name="message">The message to write.</param>
		[Conditional("TRACE")]
		public static void TraceMessage(TraceEventType eventType, int id, string message)
		{
			StackTrace callStack = new StackTrace(1);
			TraceData(callStack, eventType, id, message);
		}

		#endregion
		
		#region public static void TraceMessage(TraceEventType eventType, int id, string format, params object[] args)

		/// <summary>
		/// Formats the message using the given format and arguments and writes it
		/// to the configured trace listeners.
		/// </summary>
		/// <param name="eventType">The type of event to write.</param>
		/// <param name="id">A numeric identifier for the event.</param>
		/// <param name="format">A format string containing place holders for the arguments in <paramref name="args"/>.</param>
		/// <param name="args">The arguments for the format string.</param>
		[Conditional("TRACE")]
		public static void TraceMessage(TraceEventType eventType, int id, string format, params object[] args)
		{
			StackTrace callStack = new StackTrace(1);
			TraceData(callStack, eventType, id, string.Format(format, args));
		}

		#endregion



		
		private static void TraceData(StackTrace callStack, TraceEventType eventType, int id, params object[] data)
		{
			if (null == callStack)
				throw new ArgumentNullException("callStack");

			if (callStack.FrameCount < 1)
				throw new ArgumentException("Call stack contains no frames.", "callStack");

			List<object> newData = null;
			if (null != data && data.Length > 0)
				newData = new List<object>(data);
			else
				newData = new List<object>();

			newData.Add(CreateData("Origin", callStack.GetFrame(0)));
			newData.Add(callStack);

			string fullName = string.Format("{0}.{1}", callStack.GetFrame(0).GetMethod().DeclaringType.FullName, callStack.GetFrame(0).GetMethod().Name);
			string[] arr = fullName.Split('.');
			for (int i = 0; i < arr.Length; i++)
			{
				string sourceName = string.Join(".", arr, 0, i + 1);
				GetTraceSource(sourceName).TraceData(eventType, id, newData.ToArray());
			}
		}

	}
}
